{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:32.395339Z",
     "start_time": "2025-07-04T09:12:28.382181Z"
    }
   },
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import tensorflow as tf\n",
    "from tensorflow import keras\n",
    "from tensorflow.keras.datasets import imdb\n",
    "from tensorflow.keras.preprocessing.sequence import pad_sequences\n",
    "\n",
    "# 加载IMDB数据集\n",
    "# num_words=10000表示只保留最常见的10000个词，本身数据集有88582个词，如果未出现词，则用<UNK>表示\n",
    "# 参数说明：\n",
    "# - num_words: 保留的最常见词的数量\n",
    "# - skip_top: 跳过最常见的若干词（通常是停用词）\n",
    "# - maxlen: 序列最大长度\n",
    "# - index_from: 词索引的起始值，默认为3\n",
    "vocab_size=10000\n",
    "(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=vocab_size, index_from=3)\n",
    "\n",
    "print(f\"训练集样本数: {len(x_train)}, 测试集样本数: {len(x_test)}\")\n",
    "print(f\"标签示例: {y_train[:10]}\")  # 0表示负面评论，1表示正面评论\n",
    "\n",
    "# 查看一个样本的词索引序列\n",
    "print(f\"一个样本的词索引序列: {x_train[0][:100]}...\")\n",
    "\n",
    "# 获取词索引映射，返回一个字典（词典），key是词，value是索引（token）\n",
    "word_index = imdb.get_word_index()\n",
    "\n",
    "word_index = {word: idx + 3 for word, idx in word_index.items()}  # 0,1,2,3空出来做别的事,这里的idx是从1开始的,所以加3\n",
    "word_index.update({\n",
    "    \"[PAD]\": 0,  # 填充 token\n",
    "    \"[BOS]\": 1,  # begin of sentence\n",
    "    \"[UNK]\": 2,  # 未知 token\n",
    "    \"[EOS]\": 3,  # end of sentence\n",
    "})\n",
    "# 创建索引到词的映射\n",
    "reverse_word_index = {i: word for word, i in word_index.items()}\n",
    "\n",
    "# 将一个样本的索引序列转换为文本\n",
    "def decode_review(indices):\n",
    "    return ' '.join([reverse_word_index.get(i, '?') for i in indices])\n",
    "\n",
    "print(\"\\n解码后的样本文本:\")\n",
    "print(decode_review(x_train[0]))\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集样本数: 25000, 测试集样本数: 25000\n",
      "标签示例: [1 0 0 1 0 0 1 0 1 0]\n",
      "一个样本的词索引序列: [1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2223, 5244, 16]...\n",
      "\n",
      "解码后的样本文本:\n",
      "[BOS] this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert [UNK] is an amazing actor and now the same being director [UNK] father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for [UNK] and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also [UNK] to the two little boy's that played the [UNK] of norman and paul they were just brilliant children are often left out of the [UNK] list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "cell_type": "code",
   "id": "7f05dccc",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:32.399367Z",
     "start_time": "2025-07-04T09:12:32.395339Z"
    }
   },
   "source": [
    "reverse_word_index[4]"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'the'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 2
  },
  {
   "cell_type": "code",
   "id": "e0159de3",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:32.408586Z",
     "start_time": "2025-07-04T09:12:32.399367Z"
    }
   },
   "source": [
    "word_index['the']"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 3
  },
  {
   "cell_type": "code",
   "id": "8c7cfacb",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:32.412953Z",
     "start_time": "2025-07-04T09:12:32.409719Z"
    }
   },
   "source": [
    "x_train[1]"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1,\n",
       " 194,\n",
       " 1153,\n",
       " 194,\n",
       " 8255,\n",
       " 78,\n",
       " 228,\n",
       " 5,\n",
       " 6,\n",
       " 1463,\n",
       " 4369,\n",
       " 5012,\n",
       " 134,\n",
       " 26,\n",
       " 4,\n",
       " 715,\n",
       " 8,\n",
       " 118,\n",
       " 1634,\n",
       " 14,\n",
       " 394,\n",
       " 20,\n",
       " 13,\n",
       " 119,\n",
       " 954,\n",
       " 189,\n",
       " 102,\n",
       " 5,\n",
       " 207,\n",
       " 110,\n",
       " 3103,\n",
       " 21,\n",
       " 14,\n",
       " 69,\n",
       " 188,\n",
       " 8,\n",
       " 30,\n",
       " 23,\n",
       " 7,\n",
       " 4,\n",
       " 249,\n",
       " 126,\n",
       " 93,\n",
       " 4,\n",
       " 114,\n",
       " 9,\n",
       " 2300,\n",
       " 1523,\n",
       " 5,\n",
       " 647,\n",
       " 4,\n",
       " 116,\n",
       " 9,\n",
       " 35,\n",
       " 8163,\n",
       " 4,\n",
       " 229,\n",
       " 9,\n",
       " 340,\n",
       " 1322,\n",
       " 4,\n",
       " 118,\n",
       " 9,\n",
       " 4,\n",
       " 130,\n",
       " 4901,\n",
       " 19,\n",
       " 4,\n",
       " 1002,\n",
       " 5,\n",
       " 89,\n",
       " 29,\n",
       " 952,\n",
       " 46,\n",
       " 37,\n",
       " 4,\n",
       " 455,\n",
       " 9,\n",
       " 45,\n",
       " 43,\n",
       " 38,\n",
       " 1543,\n",
       " 1905,\n",
       " 398,\n",
       " 4,\n",
       " 1649,\n",
       " 26,\n",
       " 6853,\n",
       " 5,\n",
       " 163,\n",
       " 11,\n",
       " 3215,\n",
       " 2,\n",
       " 4,\n",
       " 1153,\n",
       " 9,\n",
       " 194,\n",
       " 775,\n",
       " 7,\n",
       " 8255,\n",
       " 2,\n",
       " 349,\n",
       " 2637,\n",
       " 148,\n",
       " 605,\n",
       " 2,\n",
       " 8003,\n",
       " 15,\n",
       " 123,\n",
       " 125,\n",
       " 68,\n",
       " 2,\n",
       " 6853,\n",
       " 15,\n",
       " 349,\n",
       " 165,\n",
       " 4362,\n",
       " 98,\n",
       " 5,\n",
       " 4,\n",
       " 228,\n",
       " 9,\n",
       " 43,\n",
       " 2,\n",
       " 1157,\n",
       " 15,\n",
       " 299,\n",
       " 120,\n",
       " 5,\n",
       " 120,\n",
       " 174,\n",
       " 11,\n",
       " 220,\n",
       " 175,\n",
       " 136,\n",
       " 50,\n",
       " 9,\n",
       " 4373,\n",
       " 228,\n",
       " 8255,\n",
       " 5,\n",
       " 2,\n",
       " 656,\n",
       " 245,\n",
       " 2350,\n",
       " 5,\n",
       " 4,\n",
       " 9837,\n",
       " 131,\n",
       " 152,\n",
       " 491,\n",
       " 18,\n",
       " 2,\n",
       " 32,\n",
       " 7464,\n",
       " 1212,\n",
       " 14,\n",
       " 9,\n",
       " 6,\n",
       " 371,\n",
       " 78,\n",
       " 22,\n",
       " 625,\n",
       " 64,\n",
       " 1382,\n",
       " 9,\n",
       " 8,\n",
       " 168,\n",
       " 145,\n",
       " 23,\n",
       " 4,\n",
       " 1690,\n",
       " 15,\n",
       " 16,\n",
       " 4,\n",
       " 1355,\n",
       " 5,\n",
       " 28,\n",
       " 6,\n",
       " 52,\n",
       " 154,\n",
       " 462,\n",
       " 33,\n",
       " 89,\n",
       " 78,\n",
       " 285,\n",
       " 16,\n",
       " 145,\n",
       " 95]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 4
  },
  {
   "cell_type": "code",
   "id": "3ab8ed72",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:32.415885Z",
     "start_time": "2025-07-04T09:12:32.412953Z"
    }
   },
   "source": [
    "# 将测试集划分为验证集和测试集\n",
    "# 从原始测试集中取前10000个样本作为验证集\n",
    "x_val = x_test[:10000]\n",
    "y_val = y_test[:10000]\n",
    "\n",
    "# 剩余的15000个样本作为测试集\n",
    "x_test = x_test[10000:]\n",
    "y_test = y_test[10000:]\n",
    "\n",
    "print(f\"验证集样本数: {len(x_val)}\")\n",
    "print(f\"测试集样本数: {len(x_test)}\")\n",
    "print(f\"验证集标签示例: {y_val[:10]}\")\n",
    "print(f\"测试集标签示例: {y_test[:10]}\")\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "验证集样本数: 10000\n",
      "测试集样本数: 15000\n",
      "验证集标签示例: [0 1 1 0 1 1 1 0 0 1]\n",
      "测试集标签示例: [1 1 0 1 0 0 1 0 1 1]\n"
     ]
    }
   ],
   "execution_count": 5
  },
  {
   "cell_type": "code",
   "id": "272d907e",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:32.418875Z",
     "start_time": "2025-07-04T09:12:32.415885Z"
    }
   },
   "source": [
    "raw_text = [\"hello world\".split(), \"tokenize text datas with batch\".split(), \"this is a test\".split()]\n",
    "raw_text"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[['hello', 'world'],\n",
       " ['tokenize', 'text', 'datas', 'with', 'batch'],\n",
       " ['this', 'is', 'a', 'test']]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 6
  },
  {
   "cell_type": "markdown",
   "id": "f2df0a9f",
   "metadata": {},
   "source": [
    "# 通过直方图来观察样本长度分布"
   ]
  },
  {
   "cell_type": "code",
   "id": "881921a0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:32.548878Z",
     "start_time": "2025-07-04T09:12:32.418875Z"
    }
   },
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 计算每个样本的长度\n",
    "train_lengths = [len(x) for x in x_train]\n",
    "\n",
    "# 绘制直方图\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.hist(train_lengths, bins=50, alpha=0.7, color='blue')\n",
    "plt.xlabel('Sample Length')\n",
    "plt.ylabel('Sample Count')\n",
    "plt.title('Training Set Sample Length Distribution')\n",
    "plt.grid(True, linestyle='--', alpha=0.7)\n",
    "\n",
    "# 计算一些统计信息\n",
    "max_length = max(train_lengths)\n",
    "min_length = min(train_lengths)\n",
    "avg_length = 500 #自定义了一个长度\n",
    "\n",
    "# 在图上显示统计信息\n",
    "plt.axvline(x=avg_length, color='r', linestyle='--', label=f'Average Length: {avg_length:.1f}')\n",
    "plt.text(max_length*0.7, plt.ylim()[1]*0.9, f'Max Length: {max_length}')\n",
    "plt.text(max_length*0.7, plt.ylim()[1]*0.85, f'Min Length: {min_length}')\n",
    "plt.text(max_length*0.7, plt.ylim()[1]*0.8, f'Average Length: {avg_length:.1f}')\n",
    "plt.legend()\n",
    "\n",
    "plt.show()\n"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1sAAAIjCAYAAAD1OgEdAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAApIpJREFUeJzt3QecE2X+x/HfFpbepCuIgiiiAkoTe0GxcdaznAX73y72886zd892imIvZ9ezYUNFsIFKR6pKERGRJr3u7vxf30cnJmFLMmw2meTzfr1CdieTyTzJb4f55Xme3+R5nucZAAAAAKBK5Vft5gAAAAAAQrIFAAAAAClAsgUAAAAAKUCyBQAAAAApQLIFAAAAAClAsgUAAAAAKUCyBQAAAAApQLIFAAAAAClAsgUAAAAAKUCyBQBmduqpp9pWW20V6LnXX3+95eXlVfk+IfX22Wcfd4O5GL7ggguq7fWGDx/uXlP3qVbW32h1tvfpp592rzd79uxqeT0AmYNkC0BG0wlKIrfqOGHLVIMHD7a9997bmjdvbnXq1LF27drZscceax988EGg7d1666325ptvJrz+woUL7eKLL7aOHTta7dq13X707NnTrrrqKlu5cqXlWtJer149y1QjRoxwicfSpUurdLtKIqL/HmvUqGFNmza13Xbbzf7xj3/YnDlzquy1ko3P6pTJ+wYgPfI8z/PS9NoAUKnnnnsu5vdnn33WPvroI/vvf/8bs/yAAw6wFi1aBH6dDRs2WGlpqdWsWTPp5xYXF7tbrVq1rLr9+9//tiuuuMIlW4cffrhLtn744Qf7+OOPrUuXLu4b9WQpWTjmmGMSeu6SJUts5513tuXLl9vpp5/uEq7FixfbxIkT7Z133nH3QXsMq4Pfq1VVybqSrddeey1jk0w/XmbNmrXR56Ik6fzzz7cHH3wwULK19dZb2wknnGCHHHKI+1v67bffbNSoUfb666+7bT/xxBN2/PHHR56jddavX29FRUWWn5+fkvis6G90U9qb7L6VlJS4Y4yOL/SCA7mlMN07AAAVOemkk2J+/+qrr1yyFb883urVq13ikSh9Ex9UYWGhu1U3nTzedNNNLtH88MMPN3p8wYIFKd8HnUCr1+LLL790vRjRlIDpRBq5Y5dddtnob/PHH3+0Aw880Pr372/bb7+9+xJAlGCl+guKVatWWd26ddP2N+orKChwNwC5h2GEAEJPvRM77rijjRkzxvbaay+XZGnokrz11lt26KGH2uabb+6+VW7fvr1LUPRNc0VztvxhUeoJePTRR93z9PwePXq4b+sTnQ+iIUXaNz13hx12KHNon3pVunfv7k489TqPPPJIQvPAFi1a5BKa3XffvczHNZwv2rp16+y6666zbbbZxu1PmzZt7Morr3TLo/dbJ6jPPPNMZEiY3pvyzJgxw51E7rrrrhs91qBBg5iT6c8//9z++te/2pZbbhl5/UsuucTWrFlT5lA8JXGHHXaY+3mLLbawgQMHuse//fZb22+//dxJdNu2be2FF14oc37MZ599Zv/3f/9nTZo0cftyyimnuN6WyiTyPm2qr7/+2g466CBr2LChi1f1TCphjebHgHoq9Z40atTIrX/aaae5LxOi6T286KKL3NC9+vXr21/+8hf7+eef3fO1HX976tUS9UL5n2/8PKJEYjYZ+oz0magX684776xwztb3339vRx99tLVs2dLFTuvWrV1v2LJlyyqNT//9mjJliv3tb3+zxo0b2x577BHzWFmef/5522677dzrdevWzcVNIvM547dZ0b6VN2froYcecu+x3msdo9TTFj/E0z++qV377ruvixf9PUS/lwAyFz1bALKChq4dfPDB7sRM36z7Qwp1kqOT9UsvvdTdf/LJJ3bttde6JOWuu+6qdLs6kV+xYoU7adfJkk5wjjrqKJs5c2alvWFffPGFG0J13nnnuRPg//znP+5EUkmEEgAZN26cO+lu1aqV3XDDDS4JvPHGG61Zs2aV7puSKc2R0pytCy+80DbbbLNy19WQLZ2Aa5/OPvts18OgpOXee++17777LjLPRMMzzzzzTDfnSuuJEsCKTqS1z3qeei4q8uqrr7ok4dxzz3Xt/+abb+yBBx6wuXPnuseiaZv6PJU86z3XCbGSVyVY//znP+3EE090n8OgQYNcEtW7d2+XQETT+kpQdFI8ffp0e/jhh10vi3+Svynv06ZQDKptOrFXUqcenqeeesolkEpI9d5H0/w7te22226zsWPH2uOPP+4++zvuuCOyjk7qX3nlFTv55JNd4vvpp5+6Lxmi6f1SG1588UXXHiVmEh1ricRsEPp8FEfqlS6PkrG+ffu6pFbxrIRLCaOGoyoBUaKZSHwqoe/QoYObP1XZTAm9Ty+//LJLVJXwKPnR36NiUwlOMpL921Fc6m++T58+7m/Cj1F9maPEO/r4oi8JtF/6DBUPGqqqOZE77bSTiyUAGUxztgAgLM4//3ydPcUs23vvvd2yQYMGbbT+6tWrN1r2f//3f16dOnW8tWvXRpb179/fa9u2beT3WbNmuW02adLEW7JkSWT5W2+95ZYPHjw4suy6667baJ/0e1FRkffDDz9Elk2YMMEtf+CBByLL+vXr5/bl559/jiz7/vvvvcLCwo22WZZrr73WrVe3bl3v4IMP9m655RZvzJgxG6333//+18vPz/c+//zzmOV6z/T8L7/8MrJM29L7kYj58+d7zZo1c9vo2LGjd84553gvvPCCt3Tp0oQ+i9tuu83Ly8vzfvzxx8gyvba2d+utt0aW/fbbb17t2rXdui+99FJk+bRp09y6+gx8Tz31lFvWrVs3b/369ZHld955p1uuzzA6dnQL8j6VRfuu9688paWlXocOHby+ffu6n6Pfm6233to74IADNoqr008/PWYbRx55pItLnz5vrTdgwICY9U499dSN3pu77rrLLVN8x0s0Zsvi/71o++U5/PDD3TrLli1zvw8bNsz9rnsZN26c+/3VV1+t8LXKi0///TrhhBPKfSy+vbqNHj06skxxWKtWLfcel3dsqGib5e2bH5P++75gwQL3Xh944IFeSUlJZL0HH3zQrffkk09udHx79tlnI8vWrVvntWzZ0jv66KPLeZcAZAqGEQLICvpWWsOr4qnnx6ceKg2923PPPV0Py7Rp0yrd7nHHHeeGI/n0XFHPVmX0jXX0N9udO3d2w9n856r3RoUsjjjiCDeEyKfha4l+W61vxtX7piIVQ4YMcb0+6jHR3JmpU6dG1lPPkXppVMBC74F/U2+KDBs2zIJQD+KECRPsnHPOcd++q6dJQ7jU86LhmtE9C9GfhYZb6fU1z0vrqIcvnnoJfOqh0lAv9Wzpm32flumxsj4P9S5E9w6o90Dzdt57771y25Oq98k3fvx4N1RO75F6Y/3t6/3Yf//93RA29a5F03sbTTGo56p3VvxhfuqNiqbeoWRVFrObwq/SqL/DsqjnShTH8cMkkxH/flXW46a/F5+GuKrQjPYhfqhxVdLfvXryBgwYEFMc5KyzznLv97vvvrvRexc9F05zIdWDVhWfC4DUItkCkBU0h6GsYgyTJ0+2I4880p3I6SRG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S6733XdfGz9+vA0YMMDOPPNMGzJkiHtc8fLOO+/YZZddZtddd52NHTvWunTpYn379rUFCxaU+5ojRoywE044wc444wwbN26ce03dJk2alNK2kmyFhA4AS5cuzbgLEiLzETNIBscaBEHchNCqVeXf1q5NfN34QgXlrVdBclNe3OhizC+//LKde+657uQ7unfjb3/7mx133HEx66tEd9OmTe3ZZ5+NVKy77bbbbOutt3aVMnVC/tprr8X0nujE/f3337du3bq5XpQvvvjCZsyYYYcffri1aNHCXdBWXyR8/PHHMa+lBEH7pO1q+y+88ILrZbnvvvsi6+hvQkmCSog3aNDA9ttvP5swYYJtiscff9y233571zOjHpqHHnoo8pjKlKs9r7/+uktUdB0qtVm9g9Eee+wxa9OmjXv8yCOPtHvuuccaNWrkHtN7fMMNN7j99Hudot/3RYsWuefoubr+1Ntvvx2oHY0aNbKWLVtGbn4pdxk0aJB7T++++27X1gsuuMCOOeYYu/fee93jihc9pvf2tNNOs06dOrnnaJ+efPLJcl/z/vvvt4MOOsiuuOIKt92bbrrJ9Z49+OCDllIeKrVs2TIdBdx9umzYsMEbOXKkuwcqtXKl/utytw1Ll6Z7bxAiHGsQBHGTudasWeNNmTLF3cf44/+IMm+HHBK7bp065a+7996x6zZtWvZ6ZSgtLfVWrFjh7svyxBNPeN27d3c/Dx482Gvfvn1k3XfeecerXbu2e75P62jZ8uXL3e8333yz17FjR++DDz7wZsyY4T311FNezZo1veHDh7vHhw0b5s7vOnfu7H344YfeDz/84C1evNgbP368N2jQIO/bb7/1vvvuO++aa67xatWq5f3444+R1+rTp4/XtWtX76uvvvLGjBnj7b333u6177333ph1+vXr540aNcpt57LLLvOaNGniXqM82p833nijzMeee+45r1WrVt7//vc/b+bMme5+s802855++mn3+KxZs9zz1Wa9P9OnT/eOOeYYr23btpG/zS+++MLLz8/37rrrLvf4wIED3TYaNmzoHl+9erXbzx122MH75Zdf3E3L/H1r3bq198ILL3jff/+9d9FFF3n16tWLaY9e67rrriu3ff52Nt98c/de9OjRw33O0TGw5557ehdffLEX7cknn/QaNGjgfl67dq1XUFDgvf766zHrnHLK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0k477bSEnnv33Xe7E8xx48bZeeedZ+eee65Nnz490H5oKN1+++3nkiAVGvjggw/s119/tWOPPTZmvWeeecbq1q1rX3/9td15551244032kcffeQeU3ECDVOsU6eOe1zJ3z//+c+Y53/zzTfuXknYL7/8EpNoDhs2zGbMmGHvvfeePf3005GbT4mdkkwg7Mcbkq2Q0EFt4sSJoam8gjSrUcNK//Uvm3vGGVaSz585EsexBkEQN4k56aST7IsvvrAff/zR3b788ku3LBGHHHKIS7K22WYbu+qqq6xp06YuYQniwQcfdImWess6duzoflbip+199913kfV0LaPrrrvOOnToYKeccopL9oYOHeoeU9KlZOnZZ5+1Ll26uB6uW265JeZ1mjVr5u41p0o9Y+oh8zVu3Njuv/9+W7dunethO/TQQyPbFrWvffv2gdqH7FYSsuMN19kCslFRkZVee63NHT3aWoboWhQAkM2UfCipUA+O53nuZyUViVDi49OQPCUvCxYsCLQfEyZMcImVhhDGUwK17bbbbvSa0qpVq8hrqldNwyK1H76ePXsmvA877LBDzDAwbfvbb7+N/H7BBRe4GxB2JFsAAADVOJTQTyIGDhyY8PNq1KgR87sSrtLS0kD7sHLlSuvXr1+ZhSWU9KTiNeOlcttAJiHZCpGwTAREBtB/WJMnW93ZszUrO917g5DhWIMgiJvEqDKe5psouejbt2/ainX873//c3OiCguDnQput9129tNPP7m5XipwIaNGjYpZp+iPkRUVDfcibhBEmOKGZCskdDDs0aNHuncDYbFmjRV27Wo76eeVK92wQiARHGsQBHGT3Eni1KlTIz+n0rJlyza69pTmT6ny32OPPWYnnHBCpNrgDz/84ErTP/744wnt1wEHHODmVPXv398Vz1ixYoVdc8017jElktK8eXOrXbu2K8DRunVrV7WwYcOGCcWN5pW98cYbMfO44i1ZssTmzJlj8+bNc7/7BUM0tNEf3jh//nx3U/tEQxX9io7Rc8gQHoUhO94wcz4kNLZb1YN0DySDmEEyONYgCOImObrArm6pNnz4cFf8Ivp2ww032Oabb+6Kc6jHSRcF3mmnnVyJ9EaNGll+gkWVlJCpxLuGJOrE98wzz4xUI1RS5Z8U/+c//7FHHnnEvebhhx+ecNyoDL3mj1Xk7bffdm3S3Dc5/vjj3e+DBg2KrKOfteyss85yv++1117udz0X4eSF7HiT54VlT9No+fLl7psYfUNUHQfHshQXF7vyrKoEFLTLf1P06xfseYMHV/WeICGrVpn9MfG5eOlSK4z6JhHI5GMNwom4gSiBU1VC9SIlUkmQuEEQmRA3yeQGRDYAAACSpmF+qmio0vBKsC6++GLbfffdKdkORCHZAgAAQNI0T0vX/NK8KZWw79Onj7v4MoA/kWyFhCabapKpP+kUSBQxg2RwrEEQxE1u0oWOdQuKuEEuxA1ztkIyZyvdmLMV3jlbrhph3brp3iMAAICcyw2oRhgSutCfrtrOBf+QkBo1zLvsMlt13nlWGqJrUSD9ONYgCOIGQRA3yIW4IdkKCQXUzJkzQxNYSLOiIiu5/Xb79uSTrZQKT0gCxxoEQdwgCOIGuRA3JFsAAAAAkAIkW0A20rc9s2dbzV9++f1nAAAAVDvGF4WEKq5oIl5YKq8gzdasscIOHWxnMytZtswNKwQSwbEGQRA3CIK4QS7EDclWSBQUFNj222+f7t1ASGMHSBTHGgRB3CAI4ga5EDcMIwwJTQKcO3duaCYDInMQM0gGxxoEQdwgCOIGuRA3JFshEbbAQuYgZpAMjjUIgrhB2OJGQ9DefPPNan9d5N7xhmQLAACgGowcOdINgTr00EMtF2RCQnP99ddb165dU9rG+NtLL70Us87w4cNtl112sZo1a9o222xjTz/99EbbGThwoG211VZWq1Yt69Wrl33zzTeVvvarr75qHTt2dM/Zaaed7L333qvStqFqkGwBAABUgyeeeMIuvPBC++yzz2zevHkpfS3P86y4uDilr4HfPfXUU/bLL79EbkcccUTksVmzZrnket9997Xx48fbgAED7Mwzz7QhQ4ZE1nn55Zft0ksvteuuu87Gjh1rXbp0sb59+7oL95ZnxIgRdsIJJ9gZZ5xh48aNc6+p26RJk1LeXoQs2fr555/tpJNOsiZNmljt2rVdZj569OiYg8W1115rrVq1co/36dPHvv/++5htLFmyxE488URr0KCBNWrUyAXeypUrY9aZOHGi7bnnni77b9Omjd15550WJvn5+dasWTN3DySDmEEyONYgCOKmcjov0Un1ueee606+o3s3/va3v9lxxx0Xs/6GDRusadOm9uyzz7rfNWTqtttus6233tqdD+mE/LXXXovpPVGvyvvvv2/dunVzvShffPGFzZgxww4//HBr0aKF1atXz3r06GEff/xxzGspQdA+abva/gsvvOB6We67777IOkuXLnVJgj5nnW/tt99+NmHChE16T5588kl3Dqj9Ug/NQw89FHls9uzZrj2vv/66S1Tq1Knj2qzewWiPPfaYO6/T40ceeaTdc8897lxQ9B7fcMMNbj/9Xqfo933RokXuOXpuhw4d7O233w7UDr1ey5YtIzeda/oGDRrk3tO7777bFXW44IIL7JhjjrF77703so72+ayzzrLTTjvNOnXq5J6jfdL7U57777/fDjroILviiivcdm+66SbXe/bggw9atssP2fEmrXv522+/2e677241atRwB4cpU6a4YGzcuHFkHSVF//nPf1zgff3111a3bl2X7a9duzayjhKtyZMn20cffWTvvPOO+8bo7LPPjjy+fPlyO/DAA61t27Y2ZswYu+uuu1y38qOPPmphoYBq3759aAILaVZYaHbeee6WT9l3JIFjDYIgbir3yiuvuIRiu+22cwmGTqT1hbJ/HjN48OCYL4rV87F69WqXDIgSLSVeOh/SOc8ll1zitvPpp5/GvM7f//53u/32223q1KnWuXNnt81DDjnEhg4d6npAdILer18/mzNnTuQ5p5xyiutpU8L2v//9z50fxfeq/PWvf3XLdL6mcymd2O+///7uC+8gnn/+eXcupvM87eutt95q//rXv+yZZ56JWe+f//ynXX755a5XaNttt3W9OX6P3ZdffmnnnHOOXXzxxe7xAw44wG655ZbIc5XAXnbZZbbDDjtEep2ik1olYscee6z7Ql7vkT6H6PYo4dQ+Vub88893iXHPnj1jPldRcqiOgmg6j/WTxvXr17v3M3od/R3p9/jEMlpl281m+WE73nhpdNVVV3l77LFHuY+XlpZ6LVu29O66667IsqVLl3o1a9b0XnzxRff7lClTFNHeqFGjIuu8//77Xl5envfzzz+73x966CGvcePG3rp162Jee7vttktoP5ctW+ZeQ/fpUlJS4v3www/uPh0OOyzYDV7OxgzCibhBEMRN5XbbbTfvvvvucz9v2LDBa9q0qTds2LCY35999tnI+ieccIJ33HHHuZ/Xrl3r1alTxxsxYkTMNs844wy3nmhbOld58803K92XHXbYwXvggQfcz1OnTt3oPOr77793y+699173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OXJAZYgZJINjDYIgbhAEcYNciJukk6077rjDrrzyShs+fLgtXrzYli9fHnNDaiigFi5cGJrAQuYgZpAMjjUIgrhBEMQNciFukh5G2KdPH3evCxtHo0AGAAAAAGxCsjVs2LBknwIAAAAAOSfpZGvvvfdOzZ6g0smArVu3ptgBElO3rpWWlPx+hfX69dO9NwgRjjUIgrhBEMQNciFukk62Pvvsswof32uvvTZlf1BJYAGJImYQBHGDIIgbBEHcIBfiJulka5999tloWfS1tpizlRp6X7/77jvbdtttraCgIN27gxAgZhAEcYMgiBsEQdwgF+Im6f633377Lea2YMEC++CDD6xHjx724YcfpmYv4QqQLFu2zN0DlVq71vKOO86an3++eWvWpHtvECIcaxAEcYMgiBvkQtwk3bPVsGHDjZYdcMABVlRUZJdeeqm74DGANCspsfz//c+amFkxvc0AAABpUWUzy1q0aGHTp0+vqs0BAAAAQKgl3bM1ceLEmN/VhffLL7/Y7bffbl27dq3KfUPcZMB27dqFpvIKMgcxg2RwrEEQxA2CIG6QC3GTdLKlhEoFMeLHSe6666725JNPVuW+IYoCqnnz5uneDYRQWA5GyAwcaxAEcYMgiBvkQtwknWzNmjVrowY3a9bMatWqVZX7hTIqr0yaNMl23HHHUFReQWbFDhGDRHGsQRDEDYIgbpALcZN0stW2bdvU7AkqpJ7ENWvWhKbyCjIHMYNkcKxBEMQNgiBukAtxE2h80aeffmr9+vWzbbbZxt3+8pe/2Oeff171ewcAAAAAIZV0svXcc89Znz59rE6dOnbRRRe5W+3atW3//fe3F154ITV7CSA5depY8dKl9s0nn7ifAQAAEIJk65ZbbrE777zTXn755UiypZ9VjfCmm24KvCN6vgpvDBgwILJs7dq1dv7551uTJk2sXr16dvTRR9uvv/4a87w5c+bYoYce6pI/TZa74oorrLi4OGad4cOH2y677GI1a9Z0PXFPP/20hY3GpHbs2DEUY1ORAfLyrKBBA9t2552toDDp0cLIYRxrEARxgyCIG+RC3CSdbM2cOdMNIYynoYTxxTMSNWrUKHvkkUesc+fOMcsvueQSGzx4sL366qtu6OK8efPsqKOOipkgp0Rr/fr1NmLECHvmmWdcInXttddG1tE+aZ19993Xxo8f75K5M888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1Fyt1atXW/v27a1OnTobFchYsmRJVe4fAAAAAORGsnXfffelZk9QIY1J7dixYyjGpiKzEDNIBscaBEHcIAjiBrkQN4GqEaL6qepjo0aN0r0bCItatcyGDXM/5tWune69QYhwrEEQxA2CIG6QC3GzSTWh165da8uXL4+5ITWKi4tt1KhR7h6oVEGBFe+xh42qW9eKQzKBFJmBYw2CIG4QBHGDXIibpJOtVatW2QUXXODq29etW9caN24cc0NqS10CySBmEARxgyCIGwRB3CDb4ybpZEuVCD/55BN7+OGHrWbNmvb444/bDTfcYJtvvrk9++yzqdlLAMnZsMHyHnrIWrz2mvsZAAAAIZizNXjwYJdU7bPPPnbaaafZnnvuadtss421bdvWnn/+eTvxxBNTs6cAErd+vRVcfLFtre72f/3LjHlbAAAAmd+zpdLu7dq1cz/r4sZ+qfc99tjDPvvss6rfQziquNK5c+fQVF5B5iBmkAyONQiCuEEQxA1yIW6STraUaM2aNcv9rLKLr7zySqTHK0yVQcKoqKgo3bsAIAdwrEEQxA2CIG6Q7XGTdLKloYMTJkxwP//973+3gQMHWq1ateySSy6xK664IhX7iD8mAo4ePTpUEwKRGYgZJINjDYIgbhAEcYNciJukky0lVRdddJH7uU+fPjZ16lR74YUXbNy4cXbxxRcntS0V2VA3oIYj6ta7d297//33Y0rLn3/++dakSROrV6+eHX300fbrr7/GbGPOnDl26KGHWp06dVyFRCV88aUghw8fbrvssosr6KH5ZU8//XSyzQYAAACA6rvOlmy11VZ21FFHuaQpWa1bt7bbb7/dxowZ4zLU/fbbzw4//HCbPHlyJLHT8MRXX33VPv30U5s3b557LZ8yWiVa69evtxEjRtgzzzzjEqlrr702so6GPGqdfffd18aPH28DBgywM88804YMGbKpTQcAAACATU+2Ro4cae+8807MMlUl3HrrrV2P0tlnn23r1q2zZPTr188OOeQQ69Chg2277bZ2yy23uB6sr776ypYtW2ZPPPGE3XPPPS4J69atmz311FMuqdLj8uGHH9qUKVPsueees65du9rBBx9sN910kxvaqARMBg0a5Pbx7rvvtu23395dI+yYY46xe++9N6l9BQAAAICUlH6/8cYbXbn3ww47zP3+7bff2hlnnGGnnnqq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qKOOijxeUlLiDkzr16+3ESNG2DPPPOMOPDrxRvbQNQR1rNAXNWXRye1//vMfGzRokH399ddWt25dd1zRCZFPJ8yTJ0+2jz76yN555x13In722WdHHl++fLkdeOCB1rZtWxszZozddddd7suARx99tFraiOqPG1FyFX38efHFF2MeJ25yj/6vUSL11Vdfuc99w4YN7jNWPFXl/02zZs1y6+y77742fvx4GzBggJ155pk2ZMiQam8zUh8zctZZZ8Ucb6K/mAlVzKj0OzJXz549vfPPPz/ye0lJibf55pt7t912W1r3C+lx3XXXeV26dCnzsaVLl3o1atTwXn311ciyqVOn6tIO3siRI93v7733npefn+/Nnz8/ss7DDz/sNWjQwFu3bl01tADVTZ//G2+8Efm9tLTUa9mypXfXXXfFxE7NmjW9F1980f0+ZcoU97xRo0ZF1nn//fe9vLw87+eff3a/P/TQQ17jxo1j4uaqq67ytttuu2pqGaozbqR///7e4YcfXu5ziBvIggULXBx8+umnVfp/05VXXuntsMMOMa913HHHeX379q2mlqG6Ykb23ntv7+KLL/bKE6aYoWcrgylb1zd/GuLjy8/Pd7+PHDkyrfuG9NFwLw3zadeunfsWWd3ooljRt0PR8aIhhltuuWUkXnS/0047xVzMWz0a+rZZ30Yj++mbPl3YPTpOGjZs6IYoR8eJhoB17949so7W1/FHPWH+OnvttZcVFRXFxJKGgvz222/V2iZUHw3J0XCd7bbbzs4991xbvHhx5DHiBrJs2TJ3v9lmm1Xp/01aJ3ob/jqcD2VfzPief/55a9q0qe2444529dVX2+rVqyOPhSlmCqv11ZCURYsWuW7S6EAS/T5t2rS07RfSRyfE6ibXiY661G+44QY392HSpEnuBFonMDrZiY8XPSa6Lyue/MeQ/fzPuaw4iI4TnVBHKywsdP8RRq+z9dZbb7QN/7HGjRuntB2ofhpCqKFf+txnzJhh//jHP+zggw92Jy4FBQXEDay0tNQN1dp9993dCbJU1f9N5a2jk+s1a9a4+afIjpiRv/3tb264sb5c1jzPq666yn0p8/rrr4cuZki2gBDRiY2vc+fOLvnSweiVV17hPxoAKXX88cdHftY3yjoGtW/f3vV27b///mndN2QGzcPRl3/Rc4mBIDETPddTxxsVdNJxRl/06LgTJgwjzGDqOtW3hfEVe/R7y5Yt07ZfyBz6pnDbbbe1H374wcWEhp4uXbq03HjRfVnx5D+G7Od/zhUdV3QfX4RHFZ5UaY5Ygk9DmfX/lI4/QtzktgsuuMAVRRk2bJi1bt06sryq/m8qbx1VvuTLxuyKmbLoy2WJPt6EJWZItjKYut27detmQ4cOjelu1e+9e/dO674hM6iksr7l0Tc+ipUaNWrExIu63DWny48X3X/77bcxJ0SqBKQDT6dOndLSBlQvDeHSf0DRcaIhFZpTEx0nOjHSXAvfJ5984o4//n94WkeV5jQXIzqWNMSVoWC5Ye7cuW7Olo4/QtzkJtVT0UnzG2+84T7v+GGiVfV/k9aJ3oa/DudD2RczZVE1QYk+3oQmZqq1HAeS9tJLL7kqYU8//bSr9HT22Wd7jRo1iqm+gtxx2WWXecOHD/dmzZrlffnll16fPn28pk2buko+cs4553hbbrml98knn3ijR4/2evfu7W6+4uJib8cdd/QOPPBAb/z48d4HH3zgNWvWzLv66qvT2CpUtRUrVnjjxo1zNx3m77nnHvfzjz/+6B6//fbb3XHkrbfe8iZOnOgqzG299dbemjVrIts46KCDvJ133tn7+uuvvS+++MLr0KGDd8IJJ0QeV4WxFi1aeCeffLI3adIkd6yqU6eO98gjj6SlzUht3Oixyy+/3FWP0/Hn448/9nbZZRcXF2vXro1sg7jJPeeee67XsGFD93/TL7/8ErmtXr06sk5V/N80c+ZMFytXXHGFq2Y4cOBAr6CgwK2L7IqZH374wbvxxhtdrOh4o/+r2rVr5+21116hjBmSrRB44IEH3EGqqKjIlYL/6quv0r1LSBOVLG3VqpWLhS222ML9roOSTyfL5513niutrAPMkUce6Q5g0WbPnu0dfPDBXu3atV2ipgRuw4YNaWgNUmXYsGHuZDn+ptLdfvn3f/3rX+6kV1/m7L///t706dNjtrF48WJ3klyvXj1XSve0005zJ9zRJkyY4O2xxx5uG4pHJXHIzrjRSZBOanQyozLebdu29c4666yNvvgjbnJPWTGj21NPPVXl/zcpRrt27er+D9TJd/RrIHtiZs6cOS6x2myzzdxxYptttnEJ07Jly0IZM3n6p3r70gAAAAAg+zFnCwAAAABSgGQLAAAAAFKAZAsAAAAAUoBkCwAAAABSgGQLAAAAAFKAZAsAAAAAUoBkCwAAAABSgGQLAAAAAFKAZAsAkPPy8vLszTfftGw0e/Zs177x48ene1cAIOeQbAEAUm7hwoV27rnn2pZbbmk1a9a0li1bWt++fe3LL7+0bJApCc2pp55qRxxxRFr3AQDwp8KonwEASImjjz7a1q9fb88884y1a9fOfv31Vxs6dKgtXrw43bsGAEDK0LMFAEippUuX2ueff2533HGH7bvvvta2bVvr2bOnXX311faXv/wlst4999xjO+20k9WtW9fatGlj5513nq1cuTLy+NNPP22NGjWyd955x7bbbjurU6eOHXPMMbZ69WqXxG211VbWuHFju+iii6ykpCTyPC2/6aab7IQTTnDb3mKLLWzgwIEV7vNPP/1kxx57rHu9zTbbzA4//HDXexVUaWmp3Xbbbbb11ltb7dq1rUuXLvbaa69FHh8+fLjrGVMC2r17d9e23XbbzaZPnx6znZtvvtmaN29u9evXtzPPPNP+/ve/W9euXd1j119/vXsf3nrrLbct3bRd38yZM937r23r9UeOHBm4PQCAxJBsAQBSql69eu6mOVHr1q0rd738/Hz7z3/+Y5MnT3ZJwyeffGJXXnllzDpKrLTOSy+9ZB988IFLJo488kh777333O2///2vPfLIIzGJjNx1110uwRg3bpxLUC6++GL76KOPytyPDRs2uCGOSmiUJGqoo/b/oIMOcr1zQSjRevbZZ23QoEGufZdccomddNJJ9umnn8as989//tPuvvtuGz16tBUWFtrpp58eeez555+3W265xSWtY8aMcUMyH3744cjjl19+uUsQtZ+//PKLuylhi9621tFQx2233dYln8XFxYHaAwBIkAcAQIq99tprXuPGjb1atWp5u+22m3f11Vd7EyZMqPA5r776qtekSZPI70899ZSn/7Z++OGHyLL/+7//8+rUqeOtWLEisqxv375uua9t27beQQcdFLPt4447zjv44IMjv2u7b7zxhvv5v//9r7fddtt5paWlkcfXrVvn1a5d2xsyZEiZ+zpr1iy3jXHjxm302Nq1a90+jhgxImb5GWec4Z1wwgnu52HDhrnnf/zxx5HH3333XbdszZo17vdevXp5559/fsw2dt99d69Lly6R3/v37+8dfvjhZe7b448/Hlk2efJkt2zq1KlltgcAUDXo2QIAVMucrXnz5tnbb7/tel7UI7XLLru4oYG+jz/+2Pbff383zE+9SieffLKb06XeLJ+GwLVv3z7ye4sWLdwwQfU8RS9bsGBBzOv37t17o9+nTp1a5r5OmDDBfvjhB7cPfq+chhKuXbvWZsyYkXTbtS214YADDohsTzf1dMVvr3PnzpGfW7Vq5e79tmhIoYZfRov/vSIVbRsAkBoUyAAAVItatWq5hEO3f/3rX27O0XXXXecq6Gk+1GGHHeYqFmqonJKbL774ws444ww3dE9JltSoUSNmm5qXVNYyzZEKSvPEunXr5obtxWvWrFmg7cm7777rEsloqswYLbotaodsSluqa9sAgLKRbAEA0qJTp06Ra1tpDpJO/DVfSXO35JVXXqmy1/rqq682+n377bcvc131uL388suuEEWDBg2qpJ1KqubMmWN777134O2oKMioUaPslFNOiSzT79GKiopiioMAANKLZAsAkFIaCvjXv/7VFXvQUDYNz1MBiDvvvNNV+ZNtttnGFaZ44IEHrF+/fq4ohYpJVBVtT6+na1CpMMarr77qeprKcuKJJ7qCGtq3G2+80Vq3bm0//vijvf76665gh34vT3z1QNlhhx1cYQoVxVBCuccee9iyZcvcPimZ69+/f0JtuPDCC+2ss85y1QpV+EIJ4cSJE10pfZ+GVA4ZMsTtR5MmTaxhw4YJbRsAkBokWwCAlNL8pF69etm9997r5igpqVJpdyUO//jHP9w6qhSo0u+qtKeS8HvttZer4Bfdi7MpLrvsMpfg3XDDDS7B0Wup4mBZNGTxs88+s6uuusqOOuooW7FihRv+p/lklfV0HX/88WWWkVfpeQ1BVJtUgl0l5dWD5rc/EUoC9Vwlbpo/psqDGoL5zTffRNbRe6r5cErINHxx2LBhLgEDAKRHnqpkpOm1AQBIOSUbAwYMcLdso/lvLVu2dCXvAQCZh54tAABCQBUNNbRSPXIFBQX24osvugqO5V0vDACQfiRbAACEgCoI6sLNqtaoYYQqmPG///3P+vTpk+5dAwCUg2GEAAAAAJACXNQYAAAAAFKAZAsAAAAAUoBkCwAAAABSgGQLAAAAAFKAZAsAAAAAUoBkCwAAAABSgGQLAAAAAFKAZAsAAAAArOr9P/jHOhyMiUyGAAAAAElFTkSuQmCC"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 7
  },
  {
   "cell_type": "markdown",
   "id": "72202119",
   "metadata": {},
   "source": [
    "# Tokenizer"
   ]
  },
  {
   "cell_type": "code",
   "id": "1f1980b6",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:32.555231Z",
     "start_time": "2025-07-04T09:12:32.549380Z"
    }
   },
   "source": [
    "import numpy as np\n",
    "\n",
    "class Tokenizer:\n",
    "    def __init__(self, word_index, reverse_word_index):\n",
    "        self.word_index = word_index\n",
    "        self.reverse_word_index = reverse_word_index\n",
    "        self.pad_token = 0  # <PAD>\n",
    "        self.start_token = 1  # <START>\n",
    "        self.unk_token = 2  # <UNK>\n",
    "        self.end_token = 3  # <END>\n",
    "    \n",
    "    def encode(self, texts, maxlen=None, padding='post', truncating='post', add_start=False, add_end=False):\n",
    "        \"\"\"\n",
    "        将文本序列转换为数字序列\n",
    "        \n",
    "        参数:\n",
    "        - texts: 文本列表，每个元素是一个词列表\n",
    "        - maxlen: 序列最大长度，如果为None则使用最长序列的长度\n",
    "        - padding: 'pre'或'post'，表示在序列前或后填充\n",
    "        - truncating: 'pre'或'post'，表示从序列前或后截断\n",
    "        - add_start: 是否添加开始标记\n",
    "        - add_end: 是否添加结束标记\n",
    "        \n",
    "        返回:\n",
    "        - 编码后的序列\n",
    "        \"\"\"\n",
    "        result = [] #编码后的序列，存储整个batch的序列\n",
    "        \n",
    "        # 计算需要的序列长度\n",
    "        batch_max_len = max([len(seq) for seq in texts]) #batch内最长序列长度\n",
    "        if add_start:\n",
    "            batch_max_len += 1\n",
    "        if add_end:\n",
    "            batch_max_len += 1\n",
    "            \n",
    "        # 如果maxlen为None或者batch内最大长度小于maxlen，使用batch内最大长度\n",
    "        if maxlen is None or batch_max_len < maxlen:\n",
    "            maxlen = batch_max_len\n",
    "        \n",
    "        for text in texts:\n",
    "            sequence = []\n",
    "            \n",
    "            # 添加开始标记\n",
    "            if add_start:\n",
    "                sequence.append(self.start_token)\n",
    "            \n",
    "            # 将词转换为索引\n",
    "            for word in text:\n",
    "                sequence.append(self.word_index.get(word, self.unk_token))  \n",
    "            \n",
    "            # 添加结束标记\n",
    "            if add_end:\n",
    "                sequence.append(self.end_token)\n",
    "            \n",
    "            # 截断序列\n",
    "            if len(sequence) > maxlen:\n",
    "                if truncating == 'pre':\n",
    "                    sequence = sequence[-maxlen:]\n",
    "                else:  # truncating == 'post'\n",
    "                    sequence = sequence[:maxlen]\n",
    "            \n",
    "            # 填充序列\n",
    "            pad_length = maxlen - len(sequence)\n",
    "            if pad_length > 0:\n",
    "                if padding == 'pre':\n",
    "                    sequence = [self.pad_token] * pad_length + sequence\n",
    "                else:  # padding == 'post'\n",
    "                    sequence = sequence + [self.pad_token] * pad_length\n",
    "            \n",
    "            result.append(sequence)\n",
    "        \n",
    "        return np.array(result)\n",
    "    \n",
    "    def decode(self, sequences):\n",
    "        \"\"\"\n",
    "        将数字序列转换回文本\n",
    "        \n",
    "        参数:\n",
    "        - sequences: 数字序列列表\n",
    "        \n",
    "        返回:\n",
    "        - 解码后的文本列表\n",
    "        \"\"\"\n",
    "        result = []\n",
    "        for sequence in sequences:\n",
    "            words = []\n",
    "            for idx in sequence:\n",
    "                if idx == self.pad_token:\n",
    "                    continue  # 跳过填充标记\n",
    "                word = self.reverse_word_index.get(idx, '?')\n",
    "                # if word not in ['[PAD]', '[BOS]', '[UNK]', '[EOS]']:\n",
    "                words.append(word)\n",
    "            result.append(' '.join(words))\n",
    "        return result\n",
    "\n",
    "# 创建Tokenizer实例\n",
    "tokenizer = Tokenizer(word_index, reverse_word_index)\n",
    "\n",
    "# 测试编码\n",
    "encoded = tokenizer.encode(raw_text, maxlen=500, padding='post', add_start=True, add_end=True)\n",
    "print(\"编码后的序列:\")\n",
    "print(encoded)\n",
    "\n",
    "# 测试解码\n",
    "decoded = tokenizer.decode(encoded)\n",
    "print(\"\\n解码后的文本:\")\n",
    "print(decoded)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "编码后的序列:\n",
      "[[    1  4825   182     3     0     0     0]\n",
      " [    1     2  3004     2    19 19233     3]\n",
      " [    1    14     9     6  2181     3     0]]\n",
      "\n",
      "解码后的文本:\n",
      "['[BOS] hello world [EOS]', '[BOS] [UNK] text [UNK] with batch [EOS]', '[BOS] this is a test [EOS]']\n"
     ]
    }
   ],
   "execution_count": 8
  },
  {
   "cell_type": "markdown",
   "id": "1f2cddf5",
   "metadata": {},
   "source": [
    "# Dataset和DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "id": "c207da73",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:34.574587Z",
     "start_time": "2025-07-04T09:12:32.555231Z"
    }
   },
   "source": [
    "import torch\n",
    "from torch.utils.data import Dataset, DataLoader, random_split\n",
    "\n",
    "class TextDataset(Dataset):\n",
    "    \"\"\"\n",
    "    文本数据集类\n",
    "    \"\"\"\n",
    "    def __init__(self, sequences, tokenizer, labels=None):\n",
    "        \"\"\"\n",
    "        初始化文本数据集\n",
    "        \n",
    "        参数:\n",
    "        - sequences: 编码后的序列\n",
    "        - tokenizer: 分词器实例\n",
    "        - labels: 标签（可选）\n",
    "        \"\"\"\n",
    "        # 将编码序列解码为文本\n",
    "        self.texts = tokenizer.decode(sequences)\n",
    "        self.tokenizer = tokenizer\n",
    "        self.labels = labels\n",
    "    \n",
    "    def __len__(self):\n",
    "        \"\"\"\n",
    "        返回数据集大小\n",
    "        \"\"\"\n",
    "        return len(self.texts)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        \"\"\"\n",
    "        获取指定索引的样本\n",
    "        \n",
    "        参数:\n",
    "        - idx: 索引\n",
    "        \n",
    "        返回:\n",
    "        - 样本（文本和标签，如果有的话）\n",
    "        \"\"\"\n",
    "        if self.labels is not None:\n",
    "            return self.texts[idx], self.labels[idx]\n",
    "        return self.texts[idx]\n",
    "\n",
    "def collate_fn(batch, tokenizer, maxlen=500):\n",
    "    \"\"\"\n",
    "    自定义批处理函数，用于在加载数据时进行编码\n",
    "    \n",
    "    参数:\n",
    "    - batch: 批次数据\n",
    "    - tokenizer: 分词器实例\n",
    "    - maxlen: 最大序列长度\n",
    "    \n",
    "    返回:\n",
    "    - 编码后的序列和标签（如果有的话）\n",
    "    \"\"\"\n",
    "    if isinstance(batch[0], tuple):\n",
    "        # print(batch)\n",
    "        # 如果批次包含标签\n",
    "        text_list = [item[0].split() for item in batch]  #batch是128样本，每个样本类型是元组，第一个元素是文本，第二个元素是标签\n",
    "        label_list = [item[1] for item in batch]\n",
    "        # print(text_list)\n",
    "        encoded = tokenizer.encode(text_list, maxlen=maxlen, padding='post', add_start=False, add_end=True)\n",
    "        sequences = torch.tensor(encoded, dtype=torch.long)\n",
    "        labels = torch.tensor(label_list, dtype=torch.float).view(-1, 1)  # 将标签reshape为二维 [batch_size, 1]\n",
    "        return sequences, labels\n",
    "    else:\n",
    "        # 如果批次只有文本\n",
    "        text_list = [item.split() for item in batch]\n",
    "        encoded = tokenizer.encode(text_list, maxlen=maxlen, padding='post', add_start=False, add_end=True)\n",
    "        sequences = torch.tensor(encoded, dtype=torch.long)\n",
    "        return sequences\n",
    "\n",
    "# 示例：创建数据集和数据加载器\n",
    "# 假设我们有训练、验证和测试数据\n",
    "# 创建对应的数据集\n",
    "train_dataset = TextDataset(x_train, tokenizer, y_train)\n",
    "val_dataset = TextDataset(x_val, tokenizer, y_val)\n",
    "test_dataset = TextDataset(x_test, tokenizer, y_test)\n",
    "\n",
    "# 创建数据加载器\n",
    "batch_size = 64\n",
    "train_dataloader = DataLoader(\n",
    "    train_dataset, \n",
    "    batch_size=batch_size, \n",
    "    shuffle=True,\n",
    "    collate_fn=lambda batch: collate_fn(batch, tokenizer)\n",
    ")\n",
    "val_dataloader = DataLoader(\n",
    "    val_dataset, \n",
    "    batch_size=batch_size, \n",
    "    shuffle=False,\n",
    "    collate_fn=lambda batch: collate_fn(batch, tokenizer)\n",
    ")\n",
    "test_dataloader = DataLoader(\n",
    "    test_dataset, \n",
    "    batch_size=batch_size, \n",
    "    shuffle=False,\n",
    "    collate_fn=lambda batch: collate_fn(batch, tokenizer)\n",
    ")\n",
    "\n",
    "# 打印数据集和数据加载器信息\n",
    "print(f\"训练集大小: {len(train_dataset)}\")\n",
    "print(f\"验证集大小: {len(val_dataset)}\")\n",
    "print(f\"测试集大小: {len(test_dataset)}\")\n",
    "\n",
    "train_dataset[0:3]\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集大小: 25000\n",
      "验证集大小: 10000\n",
      "测试集大小: 15000\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "([\"[BOS] this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert [UNK] is an amazing actor and now the same being director [UNK] father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for [UNK] and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also [UNK] to the two little boy's that played the [UNK] of norman and paul they were just brilliant children are often left out of the [UNK] list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all\",\n",
       "  \"[BOS] big hair big boobs bad music and a giant safety pin these are the words to best describe this terrible movie i love cheesy horror movies and i've seen hundreds but this had got to be on of the worst ever made the plot is paper thin and ridiculous the acting is an abomination the script is completely laughable the best is the end showdown with the cop and how he worked out who the killer is it's just so damn terribly written the clothes are sickening and funny in equal [UNK] the hair is big lots of boobs [UNK] men wear those cut [UNK] shirts that show off their [UNK] sickening that men actually wore them and the music is just [UNK] trash that plays over and over again in almost every scene there is trashy music boobs and [UNK] taking away bodies and the gym still doesn't close for [UNK] all joking aside this is a truly bad film whose only charm is to look back on the disaster that was the 80's and have a good old laugh at how bad everything was back then\",\n",
       "  \"[BOS] this has to be one of the worst films of the 1990s when my friends i were watching this film being the target audience it was aimed at we just sat watched the first half an hour with our jaws touching the floor at how bad it really was the rest of the time everyone else in the theatre just started talking to each other leaving or generally crying into their popcorn that they actually paid money they had [UNK] working to watch this feeble excuse for a film it must have looked like a great idea on paper but on film it looks like no one in the film has a clue what is going on crap acting crap costumes i can't get across how [UNK] this is to watch save yourself an hour a bit of your life\"],\n",
       " array([1, 0, 0], dtype=int64))"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 9
  },
  {
   "cell_type": "code",
   "id": "c581e1ec",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:34.588093Z",
     "start_time": "2025-07-04T09:12:34.575591Z"
    }
   },
   "source": [
    "# 示例：遍历训练数据加载器\n",
    "print(\"\\n训练数据加载器示例:\")\n",
    "for i, (batch_sequences, batch_labels) in enumerate(train_dataloader):\n",
    "    print(f\"批次 {i+1}:\")\n",
    "    print(f\"序列形状: {batch_sequences.shape}\")\n",
    "    print(f\"标签形状: {batch_labels.shape}\")\n",
    "    if i == 0:  # 只打印第一个批次\n",
    "        break\n",
    "batch_sequences"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "训练数据加载器示例:\n",
      "批次 1:\n",
      "序列形状: torch.Size([64, 500])\n",
      "标签形状: torch.Size([64, 1])\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "tensor([[   1,   54,   13,  ...,    0,    0,    0],\n",
       "        [   1,   13,  144,  ...,    0,    0,    0],\n",
       "        [   1,   13,  219,  ...,    0,    0,    0],\n",
       "        ...,\n",
       "        [   1,   13, 1713,  ...,    0,    0,    0],\n",
       "        [   1,   13,  219,  ...,    0,    0,    0],\n",
       "        [   1,   14,   20,  ...,    0,    0,    0]])"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 10
  },
  {
   "cell_type": "markdown",
   "id": "3fc9a057",
   "metadata": {},
   "source": [
    "# 搭建模型"
   ]
  },
  {
   "cell_type": "code",
   "id": "d445876c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:34.591367Z",
     "start_time": "2025-07-04T09:12:34.588093Z"
    }
   },
   "source": [
    "from torch import nn\n",
    "m = nn.AdaptiveAvgPool1d(1)  #输出形状为[batch_size, embedding_dim, 1],把最后一个维度size变成1\n",
    "input = torch.randn(1, 64, 8) \n",
    "output = m(input)\n",
    "output.shape"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([1, 64, 1])"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "id": "9693b445",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:35.324186Z",
     "start_time": "2025-07-04T09:12:34.591367Z"
    }
   },
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "class SentimentClassifier(nn.Module):\n",
    "    def __init__(self, vocab_size, embedding_dim=16, hidden_dim=128, output_dim=1):\n",
    "        super().__init__()\n",
    "        \n",
    "        # 嵌入层\n",
    "        self.embedding = nn.Embedding(vocab_size, embedding_dim)\n",
    "        \n",
    "        # 自适应池化层 - 将不同长度的序列转换为固定长度的表示\n",
    "        self.adaptive_pool = nn.AdaptiveAvgPool1d(1)\n",
    "        \n",
    "        # 全连接层\n",
    "        self.fc1 = nn.Linear(embedding_dim, hidden_dim)\n",
    "        self.fc2 = nn.Linear(hidden_dim, output_dim)\n",
    "        \n",
    "        # Dropout层，防止过拟合\n",
    "        self.dropout = nn.Dropout(0.3)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x形状: [batch_size, seq_len]\n",
    "        \n",
    "        # 通过嵌入层\n",
    "        embedded = self.embedding(x)  # [batch_size, seq_len, embedding_dim]\n",
    "        \n",
    "        # 调整维度以适应池化层\n",
    "        embedded = embedded.permute(0, 2, 1)  # [batch_size, embedding_dim, seq_len]\n",
    "        \n",
    "        # 应用自适应池化\n",
    "        pooled = self.adaptive_pool(embedded)  # [batch_size, embedding_dim, 1]\n",
    "        pooled = pooled.squeeze(2)  # [batch_size, embedding_dim]\n",
    "        \n",
    "        # 通过全连接层\n",
    "        hidden = F.relu(self.fc1(pooled))\n",
    "        hidden = self.dropout(hidden)\n",
    "        output = self.fc2(hidden)\n",
    "        \n",
    "        return output\n",
    "\n",
    "# 初始化模型\n",
    "model = SentimentClassifier(vocab_size)\n",
    "print(f\"模型结构:\\n{model}\")\n",
    "\n",
    "# 定义损失函数和优化器\n",
    "criterion = nn.BCEWithLogitsLoss() #WithLogitsLoss代表的含义是：把输出结果通过sigmoid函数，然后计算损失\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# 打印模型参数数量\n",
    "def count_parameters(model):\n",
    "    return sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
    "\n",
    "print(f\"模型参数数量: {count_parameters(model):,}\")\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "模型结构:\n",
      "SentimentClassifier(\n",
      "  (embedding): Embedding(10000, 16)\n",
      "  (adaptive_pool): AdaptiveAvgPool1d(output_size=1)\n",
      "  (fc1): Linear(in_features=16, out_features=128, bias=True)\n",
      "  (fc2): Linear(in_features=128, out_features=1, bias=True)\n",
      "  (dropout): Dropout(p=0.3, inplace=False)\n",
      ")\n",
      "模型参数数量: 162,305\n"
     ]
    }
   ],
   "execution_count": 12
  },
  {
   "cell_type": "code",
   "id": "19962cc7",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-07-04T09:12:35.337304Z",
     "start_time": "2025-07-04T09:12:35.324186Z"
    }
   },
   "source": [
    "# 设置设备\n",
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "print(f\"使用设备: {device}\")\n",
    "# 验证模型的前向计算\n",
    "# 从训练数据中获取一个批次的样本\n",
    "sample_batch, sample_labels = next(iter(train_dataloader))\n",
    "print(f\"输入形状: {sample_batch.shape}\")\n",
    "\n",
    "# 将样本移动到设备上\n",
    "sample_batch = sample_batch.to(device)\n",
    "\n",
    "# 进行前向计算\n",
    "with torch.no_grad():\n",
    "    outputs = model(sample_batch)\n",
    "    print(f\"输出形状: {outputs.shape}\")\n",
    "    \n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "使用设备: cpu\n",
      "输入形状: torch.Size([64, 500])\n",
      "输出形状: torch.Size([64, 1])\n"
     ]
    }
   ],
   "execution_count": 13
  },
  {
   "cell_type": "markdown",
   "id": "f6b527a4",
   "metadata": {},
   "source": [
    "# 训练，画图，评估"
   ]
  },
  {
   "cell_type": "code",
   "id": "4f63a223",
   "metadata": {
    "jupyter": {
     "is_executing": true
    },
    "ExecuteTime": {
     "start_time": "2025-07-04T09:12:35.337304Z"
    }
   },
   "source": [
    "from wangdao_deeplearning_train import train_two_classification_model, evaluate_two_classification_model,plot_learning_curves,EarlyStopping,ModelSaver\n",
    "\n",
    "model = SentimentClassifier(vocab_size)\n",
    "print(f\"模型结构:\\n{model}\")\n",
    "\n",
    "# 定义损失函数和优化器\n",
    "criterion = nn.BCEWithLogitsLoss() #WithLogitsLoss代表的含义是：把输出结果通过sigmoid函数，然后计算损失\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "# 将模型移动到设备上\n",
    "model = model.to(device)\n",
    "\n",
    "\n",
    "# 训练参数\n",
    "num_epochs = 20\n",
    "eval_step = 100\n",
    "\n",
    "# 训练模型\n",
    "# 创建早停和模型保存器\n",
    "early_stopping = EarlyStopping(patience=5, delta=0.001)\n",
    "model_saver = ModelSaver(save_dir='weights')\n",
    "\n",
    "model, record_dict = train_two_classification_model(\n",
    "    model=model,\n",
    "    train_loader=train_dataloader,\n",
    "    val_loader=val_dataloader,\n",
    "    criterion=criterion,\n",
    "    optimizer=optimizer,\n",
    "    device=device,\n",
    "    num_epochs=num_epochs,\n",
    "    eval_step=eval_step,\n",
    "    early_stopping=early_stopping,\n",
    "    model_saver=model_saver\n",
    ")\n",
    "\n",
    "\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "模型结构:\n",
      "SentimentClassifier(\n",
      "  (embedding): Embedding(10000, 16)\n",
      "  (adaptive_pool): AdaptiveAvgPool1d(output_size=1)\n",
      "  (fc1): Linear(in_features=16, out_features=128, bias=True)\n",
      "  (fc2): Linear(in_features=128, out_features=1, bias=True)\n",
      "  (dropout): Dropout(p=0.3, inplace=False)\n",
      ")\n",
      "训练开始，共7820步\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/7820 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "e624bd9428254981b389f51419a2174c"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "0fb25282",
   "metadata": {
    "jupyter": {
     "is_executing": true
    }
   },
   "source": [
    "# 绘制学习曲线\n",
    "plot_learning_curves(record_dict,sample_step=100)\n"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "55a18ae6",
   "metadata": {
    "jupyter": {
     "is_executing": true
    }
   },
   "source": [
    "\n",
    "\n",
    "# 在测试集上评估最终模型\n",
    "test_acc, test_loss = evaluate_two_classification_model(model, test_dataloader, device, criterion)\n",
    "print(f\"测试集准确率: {test_acc:.2f}%, 测试集损失: {test_loss:.4f}\")"
   ],
   "outputs": [],
   "execution_count": null
  },
  {
   "cell_type": "code",
   "id": "255c37e9",
   "metadata": {
    "jupyter": {
     "is_executing": true
    }
   },
   "source": [],
   "outputs": [],
   "execution_count": null
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
